package com.nchu.commondevelopmentframework.common.storage.impl;

import com.nchu.commondevelopmentframework.common.exception.file.FileNotExistException;
import com.nchu.commondevelopmentframework.common.exception.file.NullStorageConfigurationException;
import com.nchu.commondevelopmentframework.common.storage.StorageStrategy;
import com.nchu.commondevelopmentframework.common.storage.config.HuaweiObsProperties;
import com.obs.services.ObsClient;
import com.obs.services.model.*;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * @className: HuaweiObsStorageImpl
 * @description: 华为云存储实现类
 * @author: Li Chuanwei
 * @date: 2024/02/14 12:44
 * @Company: Copyright [日期] by [作者或个人]
 **/
@Component
@Slf4j
public class HuaweiObsStorageImpl implements StorageStrategy {
    @Autowired
    private HuaweiObsProperties huaweiCloudObsProperties;

    /**
     * 私有构造方法，防止外部实例化
     */
    private HuaweiObsStorageImpl() {}
    /**
     * 添加私有静态成员变量来保存单例实例
     */
    private static ObsClient obsClientInstance;

    /**
     * 通过这个方法获取实例，使用懒汉模式
     */
    private static synchronized ObsClient getOBSClientInstance(String accessKey, String secretKey, String endpoint, String bucketName) {
        if (obsClientInstance == null) {
            obsClientInstance = buildOBSClient(accessKey, secretKey, endpoint, bucketName);
        }
        return obsClientInstance;
    }

    private static ObsClient buildOBSClient(String accessKey, String secretKey, String endpoint, String bucketName){
        //判断配置是否为空
        if(!checkConfig(endpoint, accessKey, secretKey, bucketName)){
            throw new NullStorageConfigurationException("华为云的endpoint,accessKey,secretKey");
        }
        ObsClient obsClient = new ObsClient(accessKey, secretKey, endpoint);
        if (!bucketExists(obsClient, bucketName)) {
            log.info("Creating bucket " + bucketName);
            createBucket(obsClient, bucketName);
        }
        return obsClient;
    }

    /**
     * 检测配置是否为空
     *
     */
    public static boolean checkConfig(String accessKey, String secretKey, String endpoint, String bucketName) {
        return !StringUtils.isBlank(accessKey) && !StringUtils.isBlank(secretKey) && !StringUtils.isBlank(endpoint) && !StringUtils.isBlank(bucketName);
    }

    /**
     * 判断bucket是否存在
     *
     */
    private static boolean bucketExists(ObsClient obsClient, String bucketName) {
        return obsClient.headBucket(bucketName);
    }

    /**
     * 创建新的bucket
     *
     */
    private static void createBucket(ObsClient obsClient, String bucketName) {
        // 创建CreateBucketRequest对象。
        CreateBucketRequest request = new CreateBucketRequest();
        request.setBucketName(bucketName);
        // 设置桶访问权限为私有读写，默认也是私有读写
        request.setAcl(AccessControlList.REST_CANNED_PRIVATE);
        // 设置桶的存储类别为标准存储
        request.setBucketStorageClass(StorageClassEnum.STANDARD);
        // 创建桶
        ObsBucket bucket = obsClient.createBucket(request);
    }

    /**
     * 创建文件夹或目录
     *
     * @param directoryName 目录路径
     * @return true:成功 false:失败
     */
    @Override
    public boolean createDirectory(String directoryName) {
        String bucketName = huaweiCloudObsProperties.getBucketName();

        // 获取ObsClient实例
        ObsClient obsClient = getOBSClientInstance(huaweiCloudObsProperties.getAccessKey(), huaweiCloudObsProperties.getSecretKey(), huaweiCloudObsProperties.getEndpoint(), bucketName);

        // 判断是否以/结尾
        if (!directoryName.endsWith("/")) {
            directoryName += "/";
        }

        try {
            String content = "";

            // 创建PutObjectRequest对象
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, directoryName, new ByteArrayInputStream(content.getBytes()));
            // 上传字符串
            obsClient.putObject(putObjectRequest);
        } catch (Exception e) {
            // Handle error
            return false;
        }
        return true;
    }

    /**
     * 判断文件是否存在
     *
     * @param objectName 对象
     * @return 存在返回true，不存在发生异常返回false
     */
    @Override
    public boolean objectExist(String objectName) {
        String bucketName = huaweiCloudObsProperties.getBucketName();

        // 获取ObsClient实例
        ObsClient obsClient = getOBSClientInstance(huaweiCloudObsProperties.getAccessKey(), huaweiCloudObsProperties.getSecretKey(), huaweiCloudObsProperties.getEndpoint(), bucketName);

        boolean found = false;
        try {
            // 判断文件是否存在
            found = obsClient.doesObjectExist(bucketName, objectName);
        } catch (Exception e) {
            // Handle error
            return false;
        }
        return found;
    }

    /**
     * 判断文件夹是否存在【注意是文件夹而不是目录】
     *
     * @param folderName 文件夹名称（去掉前后的/）
     * @return 存在返回true，不存在发生异常返回false
     */
    @Override
    public boolean folderExist(String folderName) {
        return objectExist(folderName);
    }

    /**
     * 文件上传
     *
     * @param multipartFile 待上传文件
     * @param folderName    目录
     * @param aimFileName   最终保存到存储中的文件名，不需要后缀
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile, String folderName, String aimFileName) {
        String bucketName = huaweiCloudObsProperties.getBucketName();

        // 获取ObsClient实例
        ObsClient obsClient = getOBSClientInstance(huaweiCloudObsProperties.getAccessKey(), huaweiCloudObsProperties.getSecretKey(), huaweiCloudObsProperties.getEndpoint(), bucketName);

        // 判断文件名是否为空，若为空，则自动生成UUID
        if (!org.springframework.util.StringUtils.hasText(aimFileName)) {
            // Concatenate date, time, and UUID
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
            String dateTime = dateFormat.format(new Date());

            aimFileName = dateTime + "-" + UUID.randomUUID().toString();
        }

        // Get the file extension
        String originalFilename = multipartFile.getOriginalFilename();

        // Add file extension
        if (originalFilename != null && originalFilename.lastIndexOf(".") != -1) {
            String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
            aimFileName += suffix;
        }

        // Full file name with path
        String lastFileName = org.springframework.util.StringUtils.hasText(folderName) ? folderName + "/" + aimFileName : aimFileName;

        try (InputStream inputStream = multipartFile.getInputStream()) {
            // Set metadata for the object (optional)
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(multipartFile.getContentType());

            // Upload the file to Obs
            PutObjectResult result = obsClient.putObject(bucketName, lastFileName, inputStream, metadata);

            // Generate and return the URL for the uploaded file
            return result.getObjectUrl();
        } catch (Exception e) {
            // Handle error
        }
        return null;
    }

    /**
     * 上传文件【不指定文件夹】
     *
     * @param multipartFile 待上传文件
     * @param fileName      最终保存到存储中的文件名，不需要后缀
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile, String fileName) {
        return uploadFileAndGetURL(multipartFile,null,fileName);
    }

    /**
     * 上传文件【不指定文件夹,不指定目标文件名】
     *
     * @param multipartFile 待上传文件
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile) {
        return uploadFileAndGetURL(multipartFile,null,null);
    }

    /**
     * 拷贝文件
     *
     * @param destinationBucketName 目标bucket名称
     * @param destinationKey        目标文件名称
     * @param sourceKey             文件名称（包括目录）
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean copyObject(String destinationBucketName, String destinationKey, String sourceKey) {
        if (objectExist(sourceKey)) {
            String bucketName = huaweiCloudObsProperties.getBucketName();

            // 获取ObsClient实例
            ObsClient obsClient = getOBSClientInstance(huaweiCloudObsProperties.getAccessKey(), huaweiCloudObsProperties.getSecretKey(), huaweiCloudObsProperties.getEndpoint(), bucketName);

            // 检测目标bucket是否存在，如果不存在，则创建
            if (!bucketExists(obsClient, destinationBucketName)) {
                createBucket(obsClient, destinationBucketName);
            }

            try {
                // 创建CopyObjectRequest对象
                CopyObjectRequest copyObjectRequest = new CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey);

                // 复制文件
                CopyObjectResult result = obsClient.copyObject(copyObjectRequest);

                return true;
            } catch (Exception e) {
                log.error("拷贝失败", e);
                return false;
            }
        } else {
            throw new FileNotExistException(sourceKey);
        }
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名称
     * @param response response
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean getObject(String fileName, HttpServletResponse response) {
        if (objectExist(fileName)) {
            String bucketName = huaweiCloudObsProperties.getBucketName();

            // 获取ObsClient实例
            ObsClient obsClient = getOBSClientInstance(huaweiCloudObsProperties.getAccessKey(), huaweiCloudObsProperties.getSecretKey(), huaweiCloudObsProperties.getEndpoint(), bucketName);


            try {
                // 构造GetObjectRequest对象
                GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, fileName);

                // 获取文件输入流
                ObsObject obsObject = obsClient.getObject(getObjectRequest);
                InputStream inputStream = obsObject.getObjectContent();

                // 设置响应头
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/octet-stream");
                response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

                // 获取输出流
                OutputStream outputStream = response.getOutputStream();

                // 复制文件内容到输出流
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, len);
                }

                // 关闭输入流和输出流
                inputStream.close();
                outputStream.flush();
                outputStream.close();
                log.info("'{}'文件下载成功", fileName);
                return true;
            } catch (Exception e) {
                log.error("文件下载失败", e);
                return false;
            }
        } else {
            throw new FileNotExistException(fileName);
        }
    }


    /**
     * 获取指定路径下的所有文件对象
     *
     * @param keyPrefix 文件名称
     * @return 存储bucket内指定路径下文件对象信息
     */
    @Override
    public List<String> getBucketObjects(String keyPrefix) {
        String bucketName = huaweiCloudObsProperties.getBucketName();

        // 获取ObsClient实例
        ObsClient obsClient = getOBSClientInstance(huaweiCloudObsProperties.getAccessKey(), huaweiCloudObsProperties.getSecretKey(), huaweiCloudObsProperties.getEndpoint(), bucketName);

        List<String> objectKeys = new ArrayList<>();
        try {
            // 指定前缀列举
            ListObjectsRequest request = new ListObjectsRequest(bucketName);
            // 设置列举带有prefix前缀的对象
            request.setPrefix(keyPrefix);
            ObjectListing result = obsClient.listObjects(request);
            for (ObsObject obsObject : result.getObjects()) {
                objectKeys.add(obsObject.getObjectKey());
            }
        } catch (Exception e) {
            log.error("列举文件失败", e);
        }
        return objectKeys;
    }

    /**
     * 删除文件
     *
     * @param fileName 文件名
     * @return 成功返回true，失败发生异常返回false
     */

    @Override
    public boolean removeObject(String fileName) {
        if(objectExist(fileName)){
            String bucketName = huaweiCloudObsProperties.getBucketName();

            // 获取ObsClient实例
            ObsClient obsClient = getOBSClientInstance(huaweiCloudObsProperties.getAccessKey(), huaweiCloudObsProperties.getSecretKey(), huaweiCloudObsProperties.getEndpoint(), bucketName);

            try {
                // 删除文件
                obsClient.deleteObject(bucketName, fileName);
                return true;
            } catch (Exception e) {
                log.error("文件删除失败", e);
                return false;
            }
        }
        else {
            throw new FileNotExistException(fileName);
        }
    }
}
